<?php
/* 搜索引擎
*
*
*/

namespace hpnWse\nSe {

require_once(__DIR__ . '/(0)Base.php');

use \hpnWse\stNumUtil;
use \hpnWse\stStrUtil;
use \hpnWse\stObjUtil;
use \hpnWse\stAryUtil;
use \hpnWse\stDateUtil;
use \hpnWse\nSe\stBase as stSeBase;

/// 中文分词 - 隐马尔科夫
class tCws_Hmm extends atCws
{
	// 训练数据（JSON）
	public static $sc_TrainData = null;

	// 最小double
	public static $i_MinDbl = -3.14e+100;

	/// 构造
	public function __construct()
	{
		// 加载训练数据集
		if (!self::$sc_TrainData)
		{
			// c_EmitPmtx四行长度：6857, 7439, 6409, 14519
			self::$sc_TrainData = stObjUtil::cDcdJson(file_get_contents(__DIR__ . '/Dic/HmmTrainData.json'));
		}
	}

	/// 运行
	public function vdRun(&$a_Rst, $a_Text, $a_PsrvPhr, $a_WithKind)
	{
		// 拆行，分别处理每行		
		$l_Paras = array();
		stBase::cSplTextToParas($l_Paras, $a_Text);
		$l_ParasLen = count($l_Paras);
		for ($l=0; $l<$l_ParasLen; ++$l)
		{
		//	print_r(stObjUtil::cEcdJson($l_Paras[$l])); echo '<br>';
			$this->cSgmtPara($a_Rst, $l_Paras[$l], count($l_Paras[$l]), $a_PsrvPhr, $a_WithKind);
		}
	}

	/// 对段落分词
	/// a_Rst：String[]，每个元素是一个词语
	/// a_Chas: String[]，不会被修改
	public function cSgmtPara(&$a_Rst, &$a_Chas, $a_ChasLen, $a_PsrvPhr, $a_WithKind)
	{
		// 先切分成短语，然后逐条处理
		$l_Phrs = array();
		$this->dSplStnToPhrsByDic($l_Phrs, $a_Chas, $a_ChasLen);
		$l_PhrsLen = count($l_Phrs);
		for ($p=0; $p<$l_PhrsLen; ++$p)
		{
			// 只处理i_CC，其他种类直接装入结果
			$l_Phr01 = &$l_Phrs[$p];
			if ($a_PsrvPhr || ('i_CC' !== $l_Phr01[0]))
			{
				$this->dApdToRst($a_Rst, $l_Phr01[1], ($a_WithKind ? $l_Phr01[0] : null));
				continue;
			}

			// 如果有词典，根据词典进一步拆分
			if ($this->cHasDic())
			{
				$l_Spans = array();
				$this->dSplPhrToSpansByDic($l_Spans, $l_Phr01[1], count($l_Phr01[1]));
				$l_SpansLen = count($l_Spans);
				for ($s=0; $s<$l_SpansLen; ++$s)
				{
					if (is_string($l_Spans[$s]))
					{ $a_Rst[] = $a_WithKind ? array('i_CC', $l_Spans[$s]) : $l_Spans[$s]; }
					else
					{ self::scSgmtPhr_CC($a_Rst, $l_Spans[$s], count($l_Spans[$s]), $a_WithKind); }
				}
			}
			else
			{
				self::scSgmtPhr_CC($a_Rst, $l_Phr01[1], count($l_Phr01[1]), $a_WithKind);
			}
		}
	}

	/// 对简体中文短语分词
	///【警告：必须全是简体汉字】
	/// a_Rst：String[]，每个元素是一个词语
	/// a_Chas: String[]，不会被修改，必须是训练集包含的字符，否则结果未定义
	/// a_WithKind: Boolean，若true，则装入a_Rst的是['i_CC', 词语]
	public static function scSgmtPhr_CC(&$a_Rst, &$a_Chas, $a_ChasLen, $a_WithKind = false)
	{
		// 特殊处理
		if (0 === $a_ChasLen)
		{ return; }
		
		if (1 === $a_ChasLen)
		{
			$a_Rst[] = $a_WithKind ? array('i_CC', $a_Chas[0]) : $a_Chas[0];
			return;
		}

		// HMM训练数据
		$i_MinDbl = self::$i_MinDbl;
		$i_StaIdx = &self::$sc_TrainData['c_StaIdx'];
		$i_InitPary = &self::$sc_TrainData['c_InitPary'];
		$i_TsitPmtx = &self::$sc_TrainData['c_TsitPmtx'];
		$i_EmitPmtx = &self::$sc_TrainData['c_EmitPmtx'];
	//	echo count($i_EmitPmtx[0]) . ', ' . count($i_EmitPmtx[1]) . ', ' . count($i_EmitPmtx[2]) . ', '. count($i_EmitPmtx[3]) . '<br>';
		
		// 初始化，注意由于取了对数，乘法变成加法
		$l_Wgts = array(); // 概率
		$l_Path = array(); // 父指针
		for ($j=0; $j<4; ++$j)
		{
			$l_Wgts[$j] = stAryUtil::cNew($a_ChasLen, $i_MinDbl);
			$l_Wgts[$j][0] = $i_InitPary[$j] + self::seFchEmitPblt($i_EmitPmtx[$j], $a_Chas[0]); // 初始概率
			$l_Path[$j] = stAryUtil::cNew($a_ChasLen, -1);
		}

		// 遍历各个汉字，下标i从1开始是因为刚才初始化的时候已经对0初始化了
		for($i=1; $i<$a_ChasLen; ++$i)
		{
			// 遍历可能的状态
			for($j=0; $j<4; ++$j) 
			{
		    	$l_Wgts[$j][$i] = $i_MinDbl;
		    	$l_Path[$j][$i] = -1;

				// 遍历前一个字可能的状态，从状态k转移至j
				for($k=0; $k<4; ++$k)
				{
					$l_Wgt = $l_Wgts[$k][$i-1] + $i_TsitPmtx[$k][$j] + self::seFchEmitPblt($i_EmitPmtx[$j], $a_Chas[$i]);
					if (($l_Path[$j][$i] < 0) || ($l_Wgt > $l_Wgts[$j][$i])) // 更新最大概率
					{
						// if (1 == $j && 2 == $i)
						// {
						//  	echo $a_Chas[$i] . ', k=' . $k . ', W=' . $l_Wgt . '<br>';
						// 	print_r(stObjUtil::cEcdJson($l_Wgts)); echo '<br>';
						// 	print_r(stObjUtil::cEcdJson($l_Path)); echo '<br>';
						// 	echo '<br>';
						// // 	continue;
						// }

						$l_Wgts[$j][$i] = $l_Wgt;
						$l_Path[$j][$i] = $k;
					}
				}
		    }
		}

		// print_r(stObjUtil::cEcdJson($l_Wgts)); echo '<br>';
		// print_r(stObjUtil::cEcdJson($l_Path)); echo '<br>';

		// l_Path表示“前一个”，所以最后一项专门处理，而各行的[0]都是-1（表示没有前一个）
		// 反向遍历，从头压入，可得完整路径
		$l_Stas = array(($l_Wgts[1][$a_ChasLen-1] >= $l_Wgts[3][$a_ChasLen-1]) ? 1 : 3);
		for ($i=$a_ChasLen-1; $i>0; --$i)
		{
			array_unshift($l_Stas, $l_Path[$l_Stas[0]][$i]);
		}

		$l_Word = '';
		for ($i=0; $i<$a_ChasLen; ++$i)
		{
			$l_Sta = $l_Stas[$i];
			$l_Cha = $a_Chas[$i];
		//	echo $i_StaIdx[$l_Sta] . ', ';

			if (0 == $l_Sta) // B
			{
				$l_Word = $l_Cha;
			}
			else
			if (1 == $l_Sta) // E
			{
				$l_Word .= $l_Cha;
				$a_Rst[] = $a_WithKind ? array('i_CC', $l_Word) : $l_Word;
				$l_Word = '';
			}
			else
			if (2 == $l_Sta) // M
			{
				$l_Word .= $l_Cha;
			}
			else // S
			{
				$a_Rst[] = $a_WithKind ? array('i_CC', $l_Cha) : $l_Cha;
				$l_Word = '';
			}
		}
	}

	private static function seFchEmitPblt(&$i_Prow, $a_Cha)
	{
		return isset($i_Prow[$a_Cha]) ? $i_Prow[$a_Cha] : self::$i_MinDbl;
	}
}


} // namespace hpnWse\nSe

//////////////////////////////////// OVER ////////////////////////////////////